﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using EasyLinkerSDK.Client.Interface;
using EasyLinkerSDK.Client.Message;
using EasyLinkerSDK.Common.Json;
using EasyLinkerSDK.Common.Tools;
using EasyLinkerSDK.Enum;
using EasyLinkerSDK.Model.Client.Common.Interface;
using EasyLinkerSDK.Model.Client.Login;
using EasyLinkerSDK.Model.Client.Msg;
using EasyLinkerSDK.Model.Server.Common.Interface;
using EasyLinkerSDK.Model.Server.Common.ServerError;
using EasyLinkerSDK.Model.Server.Common.ServerMessage;
using EasyLinkerSDK.Model.Server.Msg;
using EasyLinkerSDK.Model.Server.Query;
using EasyLinkerSDK.Model.User;
using GodSharp.Sockets;
using static EasyLinkerSDK.Client.Interface.EventHandler;

namespace EasyLinkerSDK.Client.TCP
{
    public class TCPClient : IELClient
    {
        #region 定义

        private TcpClient _client = null;

        private ClientInfo _clientInfo = null;

        private readonly TcpClientOptions _clientOptions = null;

        private bool _isLogin = false;

        private bool _isDoingReconnect = false;

        private Task _heartTask = null;

        private CancellationTokenSource _cts = null;

        #endregion

        public bool IsConnected
        {
            get
            {
                if (_client == null)
                {
                    return false;
                }
                else
                {
                    return _client.Running;
                }
            }
        }

        public event MessageReceivedEventHandler DoMessageReceived;

        public event ClientLogEventHandler DoClientLog;

        public TCPClient(ClientInfo clientInfo)
        {
            if (clientInfo == null
                || string.IsNullOrEmpty(clientInfo.DeviceId)
                || string.IsNullOrEmpty(clientInfo.UserId)
                || string.IsNullOrEmpty(clientInfo.Key)
                || string.IsNullOrEmpty(clientInfo.Server))
            {
                throw new NullReferenceException("Param can not null");
            }

            _clientInfo = clientInfo;
            if (Dns.GetHostEntry(_clientInfo.Server).AddressList.Length > 0)
            {
                _clientOptions = new TcpClientOptions(new IPEndPoint(Dns.GetHostEntry(_clientInfo.Server).AddressList[0], 8883)) { ConnectTimeout = -1 };
            }
            else
            {
                if (IPAddress.TryParse(_clientInfo.Server, out IPAddress ip))
                {
                    _clientOptions = new TcpClientOptions(new IPEndPoint(ip, 8883)) { ConnectTimeout = -1 };
                }
                else if (Dns.GetHostEntry("easylinker.net").AddressList.Length > 0)
                {
                    _clientOptions = new TcpClientOptions(new IPEndPoint(Dns.GetHostEntry("easylinker.net").AddressList[0], 8883)) { ConnectTimeout = -1 };
                }
                else
                {
                    throw new Exception("Server address error");
                }
            }
        }

        public async Task<bool> Start()
        {
            try
            {
                await Task.Run(() => { return; });
                _client = new TcpClient(_clientOptions)
                {
                    OnConnected = ((c) => OnConnect(c)),
                    OnDisconnected = ((c) => OnDisconnect(c)),
                    OnException = ((c) => OnException(c)),
                    OnReceived = ((c) => OnReceived(c)),
                    OnStarted = ((c) => OnStarted(c)),
                    OnStopped = ((c) => OnStopped(c))
                };
                _client.Start();
                Stopwatch sw = new Stopwatch();
                sw.Start();
                while (!_client.Running)
                {
                    if (sw.Elapsed.TotalMilliseconds > 3000)
                    {
                        break;
                    }
                    Thread.Sleep(10);
                }
                sw.Stop();
                if (_client.Running)
                {
                    if (DoLogin() && StartHeart())
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                DoClientLog?.Invoke($"Client TCP connect error.", ex);
                return false;
            }
        }

        public async Task<bool> Stop()
        {
            try
            {
                await Task.Run(() => { return; });

                _isDoingReconnect = true;
                _client.Stop();
                Stopwatch sw = new Stopwatch();
                sw.Start();
                while (_client.Running)
                {
                    if (sw.Elapsed.TotalMilliseconds > 3000)
                    {
                        break;
                    }
                    Thread.Sleep(10);
                }
                sw.Stop();
                if (!_client.Running && StopHeart())
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                DoClientLog?.Invoke("Client TCP disconnect error.", ex);
                return false;
            }
        }

        public void Send(IClientMessage clientMsg)
        {
            if (clientMsg == null)
            {
                return;
            }
            if (!IsConnected)
            {
                DoClientLog?.Invoke("Send message failed.", new Exception("Server not connect."));
                return;
            }
            string clientMsgObj = JsonHelper.SerializeObject(clientMsg);
            _client.Connection.Send(clientMsgObj, Encoding.UTF8);
        }

        #region 事件

        private void OnConnect(NetClientEventArgs<ITcpConnection> args)
        {
            _isDoingReconnect = false;
        }

        private void OnDisconnect(NetClientEventArgs<ITcpConnection> args)
        {
            if (!StopHeart())
            {
                DoClientLog?.Invoke("Stop heart thread failed.", null);
                return;
            }
            if (!_clientInfo.IsReconnect)
            {
                DoClientLog?.Invoke("Client tcp disconnect from server.", null);
                return;
            }
            if (_isDoingReconnect)
            {
                return;
            }
            Task.Run(async () =>
            {
                _isDoingReconnect = true;
                bool connectState = false;
                while (!connectState)
                {
                    DoClientLog?.Invoke("Retry to connect server.", null);
                    connectState = await Start();
                    if (connectState)
                    {
                        DoClientLog?.Invoke("Success to reconnect server.", null);
                        break;
                    }
                    Thread.Sleep(10000);  //每10s重连一次
                }
            });
        }

        private void OnException(NetClientEventArgs<ITcpConnection> args)
        {
            if (args.Exception != null)
            {
                //DoClientLog?.Invoke("Tcp client exception.", args.Exception);
            }
        }

        private void OnReceived(NetClientReceivedEventArgs<ITcpConnection> args)
        {
            try
            {
                byte[] receive = args.Buffers;
                if (receive.Length == 0)
                {
                    return;
                }
                string payload = Encoding.UTF8.GetString(receive);
                ApplicationMessageReceived(payload);
            }
            catch (Exception ex)
            {
                DoClientLog?.Invoke("Fetch client tcp data error.", ex);
            }
        }

        private void OnStarted(NetClientEventArgs<ITcpConnection> c)
        {

        }

        private void OnStopped(NetClientEventArgs<ITcpConnection> c)
        {
            Console.WriteLine("This is first time stop");
        }
        #endregion

        #region 其它方法

        #region Login

        /// <summary>
        /// 检查是否登录
        /// </summary>
        /// <returns></returns>
        public bool DoLogin()
        {
            //Send logon request
            Login login = new Login()
            {
                Device = _clientInfo.DeviceType,
                Uid = _clientInfo.UserId,
                Key = _clientInfo.Key,
                ProtocolType = Enum.ProtocolType.Tcp,
                MessageId = Guid.NewGuid().ToString("N"),
                DeviceId = _clientInfo.DeviceId,
                Time = Time.Timestamp(),
                Data = null,
            };
            Send(login);

            //检查是否登录
            Stopwatch sw = new Stopwatch();
            sw.Start();
            while (true)
            {
                if (sw.Elapsed.TotalMilliseconds > 3500)
                {
                    break;
                }
                if (_isLogin)
                {
                    return true;
                }
                Thread.Sleep(10);
            }
            sw.Stop();
            return false;
        }
        #endregion

        #region Receive
        private void ApplicationMessageReceived(string payload)
        {
            if (string.IsNullOrEmpty(payload))
            {
                return;
            }
            try
            {
                ServerMessage message = JsonHelper.DeserializeJsonToObject<ServerMessage>(payload);
                if (message == null)
                {
                    return;
                }
                MessageProcessor processor = new MessageProcessor();
                IServerMessage iserver = null;
                switch (message.Type)
                {
                    case MessageType.Login:
                        LoginResult loginResult = JsonHelper.DeserializeJsonToObject<LoginResult>(payload);
                        if (loginResult != null && loginResult.Data != null)
                        {
                            if (loginResult.Data.Code == MessageErrorType.SUCCESS)
                            {
                                _isLogin = true;
                            }
                            else
                            {
                                DoClientLog?.Invoke($"Login failed. {loginResult.Data.Message}", null);
                            }
                        }
                        break;
                    case MessageType.Control:
                        if (message.Data == null)
                        {
                            return;
                        }
                        message.Data.Content = payload;
                        iserver = processor.BuildControlMessage(message);
                        if (iserver != null)
                        {
                            DoMessageReceived?.Invoke(iserver);
                        }
                        break;
                    case MessageType.Error:
                    case MessageType.Success:
                        ServerError serverError = JsonHelper.DeserializeJsonToObject<ServerError>(payload);
                        if (serverError != null)
                        {
                            DoMessageReceived?.Invoke(serverError);
                        }
                        break;
                    case MessageType.Heart:
                        Msg heartMsg = new Msg()
                        {
                            Device = DeviceType.other,
                            Uid = _clientInfo.UserId,
                            Key = _clientInfo.Key,
                            ProtocolType = ProtocolType.Tcp,
                            Type = MessageType.Heart,
                            Action = ActionName.Other,
                            MessageId = Guid.NewGuid().ToString("N"),
                            DeviceId = _clientInfo.DeviceId,
                            Time = Time.Timestamp(),
                            Data = null,
                        };
                        Send(heartMsg);
                        break;
                    case MessageType.Msg:
                        MsgResult msg = JsonHelper.DeserializeJsonToObject<MsgResult>(payload);
                        if (msg != null)
                        {
                            DoMessageReceived?.Invoke(msg);
                        }
                        break;
                    case MessageType.Query:
                        QueryResult query = JsonHelper.DeserializeJsonToObject<QueryResult>(payload);
                        if (query != null)
                        {
                            DoMessageReceived?.Invoke(query);
                        }
                        break;
                    default:
                        break;
                }
            }
            catch (Exception ex)
            {
                DoClientLog?.Invoke("Application message received error.", ex);
                return;
            }
        }
        #endregion

        #region Heart

        private bool StartHeart()
        {
            try
            {
                if (_heartTask != null)
                {
                    if (!StopHeart())
                    {
                        DoClientLog?.Invoke("Try to restart heart task failed.", null);
                        return false;
                    }
                }
                var ct = (_cts = new CancellationTokenSource()).Token;
                _heartTask = new Task(() => HeartHelper(ct), ct, TaskCreationOptions.LongRunning);
                _heartTask.Start();
                if (_heartTask.Status == TaskStatus.Created || _heartTask.Status == TaskStatus.Running || _heartTask.Status == TaskStatus.WaitingToRun)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                StopHeart();
                DoClientLog?.Invoke("Start heart task failed.", ex);
                return false;
            }
        }

        private bool StopHeart()
        {
            try
            {
                if (_heartTask != null && _heartTask.Status == TaskStatus.Running || _heartTask.Status == TaskStatus.WaitingToRun)
                {
                    _cts.Cancel();
                    while (true)
                    {
                        Thread.Sleep(100);
                        if (_heartTask.IsCompleted
                            || _heartTask.IsCanceled
                            || _heartTask.Status == TaskStatus.RanToCompletion
                            || _heartTask.Status == TaskStatus.Canceled
                            || _heartTask.Status == TaskStatus.Faulted)
                        {
                            break;
                        }
                    }
                    _cts = null;
                    _heartTask = null;
                    return true;
                }
                else if (_heartTask.IsCompleted
                            || _heartTask.IsCanceled
                            || _heartTask.Status == TaskStatus.RanToCompletion
                            || _heartTask.Status == TaskStatus.Canceled
                            || _heartTask.Status == TaskStatus.Faulted)
                {
                    _cts = null;
                    _heartTask = null;
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                DoClientLog("Stop heart thread failed.", ex);
                return false;
            }
        }

        private void HeartHelper(CancellationToken ct)
        {
            while (IsConnected)
            {
                try
                {
                    int start = 3600000;
                    while (true)
                    {
                        if (ct.IsCancellationRequested)
                        {
                            ct.ThrowIfCancellationRequested();
                        }
                        Thread.Sleep(100);
                        start = start - 100;
                        if (start <= 0)
                        {
                            break;
                        }
                    }
                    if (!ct.IsCancellationRequested)
                    {
                        Msg heart = new Msg()
                        {
                            Device = _clientInfo.DeviceType,
                            Uid = _clientInfo.UserId,
                            Key = _clientInfo.Key,
                            ProtocolType = Enum.ProtocolType.Tcp,
                            Type = MessageType.Heart,
                            Action = ActionName.Other,
                            MessageId = Guid.NewGuid().ToString("N"),
                            DeviceId = _clientInfo.DeviceId,
                            Time = Time.Timestamp(),
                            Data = null,
                        };
                        Send(heart);
                    }
                    else
                    {
                        ct.ThrowIfCancellationRequested();
                    }
                }
                catch (OperationCanceledException)
                {
                    //取消
                    break;
                }
                catch (Exception ex)
                {
                    DoClientLog("Send heart message error", ex);
                }
            }
        }

        #endregion
        #endregion
    }
}
